package com.itxh.mybatis.dao;

import com.itxh.mybatis.model.*;
import com.itxh.mybatis.po.User;

import javax.sql.DataSource;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.*;


/**
 * 持久层代码--使用JDBC实现
 * @author think
 *
 */
public class UserDaoImplWithJdbc3 implements UserDao {

	private Configuration configuration;

	public UserDaoImplWithJdbc3(Configuration configuration) {
		this.configuration = configuration;
	}

	@Override
	public User queryUserById(Integer id) {
		return selectOne("test.queryUserById",id);
	}
	@Override
 	public List<User> queryUserByName(String username) {
		return selectList("test.queryUserByName",username);
	}

	// SqlSessionFactoryBuilder：使用构建者模式去创建SqlSessionFactory
	// SqlSessionFactory:很多个千篇一律的对象
	// 	 SqlSession openSession();
	// Configuration
	//SqlSession.selectOne(String statementId, Object param)
	//SqlSession.selectList(String statementId, Object param)

	// statement执行，分为多种：批处理、简单实现
	// 加入缓存的使用
	// 【Executor】：
	// CachingExecutor：解决的是二级缓存的使用
	// BaseExecutor：解决的是一级缓存的使用
	// SimpleExecutor：正常Statement、PrepareStatement进行执行
	// BatchExecutor：批处理执行Statement
	// ReuseExecutor：重复使用Statement去完成相关SQL操作

	// 定义【StatementHandler】：用于统一处理各种类型的Statement，包括创建、参数设置和执行、结果处理
	// PreparedStatementHandler
	// SimpleStatementHandler
	// CallableStatementHandler

	// 定义【ParameterHandler】：专门解决参数设置
	// 定义【ResultSetHandler】：专门解决结果映射

	private <T> T selectOne(String statementId, Object param){
		List<T> list = selectList(statementId, param);
		if (list != null || list.size() == 1){
			return list.get(0);
		}
		return (T) list;
	}
	private <T> List<T> selectList(String statementId, Object param){
		Connection connection = null;
		Statement statement = null;

		ResultSet rs = null;

		List<T> results = new ArrayList<>();
		try {
			DataSource dataSource = configuration.getDataSource();
			MappedStatement mappedStatement = configuration.getMappedStatementById(statementId);
			// 1、获取连接
			connection = dataSource.getConnection();

			// 2、获取SQL语句
			SqlSource sqlSource = mappedStatement.getSqlSource();
			BoundSql boundSql = sqlSource.getBoundSql(param);
			String sql = boundSql.getSql();

			// 3、statement
			String statementType = mappedStatement.getStatementType();
			statement = getStatement(connection,sql,statementType);

			// 4、设置参数
			setParameters(statement,param,statementType,boundSql.getParameterMappings());

			// 5、执行statement
			rs = executeStatement(statement,sql,statementType);

			// 6、处理结果集
			results = handleResultSet(rs,mappedStatement.getResultTypeClass());
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			// 6、释放资源
			releaseConnection(connection,statement,rs);
		}
		return results;
	}

	private void releaseConnection(Connection connection, Statement statement, ResultSet rs) {
		if (rs != null) {
			try {
				rs.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
		if (statement != null) {
			try {
				statement.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
		if (connection != null) {
			try {
				connection.close();
			} catch (SQLException e) {
			}
		}
	}

	private <T> List<T> handleResultSet(ResultSet rs, Class resultType) throws Exception{
		if (rs == null) {
			return null;
		}
		List<T> results = new ArrayList<>();
		Object result = null;
		while (rs.next()){
			result = newObject(resultType);

			// 结果集元数据
			ResultSetMetaData metaData = rs.getMetaData();
			int columnCount = metaData.getColumnCount();
			for (int i = 1; i <= columnCount; i++) {
				// 下标从1开始
				String columnName = metaData.getColumnName(i);
				// 通过反射，给对象赋值
				setPropretyValue(result,columnName,rs.getObject(i));
			}

			results.add((T) result);
		}
		return results;
	}
	private void setPropretyValue(Object result, String columnName, Object value) throws Exception{
		Class<?> aClass = result.getClass();
		Field field = aClass.getDeclaredField(columnName);
		field.setAccessible(true);
		field.set(result,value);
	}
	private Object newObject(Class resultType) throws Exception{
		Constructor constructor = resultType.getConstructor();
		return constructor.newInstance();
	}

	private ResultSet executeStatement(Statement statement, String sql, String statementType) throws Exception{
		if(statementType.equals("prepared")){
			PreparedStatement preparedStatement = (PreparedStatement) statement;
			return preparedStatement.executeQuery();
		}else if(statementType.equals("statement")){
			return statement.executeQuery(sql);
		}else if(statementType.equals("callable")){
			// TODO
		}else{
			// TODO
		}
		return null;
	}
	private void setParameters(Statement statement, Object param, String statementType, List<ParameterMapping> parameterMappings) throws Exception {
		if (statementType.equals("prepared")){
			PreparedStatement preparedStatement = (PreparedStatement) statement;

			// 判断入参类型，不同类型的参数，有不同的设置方法
			if(isSimpleType(param)){
				// TODO 可以针对不同类型，去设置参数
				// preparedStatement.setInt(1,param);
				// 可以使用类型处理器
				preparedStatement.setObject(1,param);
			}else if (isMap(param)){
				Map map = (Map) param;
				// 普通类
				if (parameterMappings != null){
					for (int i = 0; i < parameterMappings.size(); i++) {
						ParameterMapping pm = parameterMappings.get(i);

						String parameterName = pm.getParameterName();

						// 利用反射，可以通过属性名，得到对象的属性值
						Object value = map.get(parameterName);
						preparedStatement.setObject(i+1,value);

					}
				}
			}else if(isList(param)){
				// TODO
			}else{
				// 普通类
				if (parameterMappings != null){
					for (int i = 0; i < parameterMappings.size(); i++) {
						ParameterMapping pm = parameterMappings.get(i);

						String parameterName = pm.getParameterName();

						// 利用反射，可以通过属性名，得到对象的属性值
						Object value = resolveValue(param,parameterName);
						preparedStatement.setObject(i+1,value);

					}
				}
			}
		}
	}

	private Object resolveValue(Object param, String parameterName) throws Exception{
		Class<?> aClass = param.getClass();
		Field field = aClass.getDeclaredField(parameterName);
		field.setAccessible(true);
		return field.get(param);
	}
	private boolean isList(Object param) {
		return (param instanceof List);
	}
	private boolean isMap(Object param) {
		return (param instanceof Map);
	}
	private static Set<Class> simpleTypeSet = new HashSet<>();
	static{
		simpleTypeSet.add(Integer.class);
		simpleTypeSet.add(String.class);
		simpleTypeSet.add(Double.class);
		// TODO 补全简单类型
	}
	private boolean isSimpleType(Object param) {

		return (simpleTypeSet.contains(param.getClass()));
	}
	private Statement getStatement(Connection connection, String sql, String statementType)throws Exception {

		if (statementType.equals("prepared")){
			return connection.prepareStatement(sql);
		}else if (statementType.equals("statement")){
			return connection.createStatement();
		}else if(statementType.equals("callable")){
			return connection.prepareCall(sql);
		}else{
			return null;
		}

	}


}
